package jinyilw.swing.graphics;

import jinyilw.common.JvmTools;
import jinyilw.common.OSTools;
import jinyilw.common.log.LogTools;
import jinyilw.swing.SwingTools;

import javax.swing.Icon;
import javax.swing.plaf.basic.BasicGraphicsUtils;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Paint;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.lang.reflect.Field;

public class GraphicsTools
{
	private static GraphicsConfiguration defaultConfiguration;
	private static Graphics2D helpGraphics;
	private static Double systemScaleFactor;
	private static Stroke dottedStroke;
	private static Stroke largeStroke;
	private static Stroke roundStroke;

	private GraphicsTools()
	{
	}

	public static GraphicsConfiguration getDefaultConfiguration()
	{
		if (defaultConfiguration == null)
		{
			defaultConfiguration = GraphicsEnvironment
					.getLocalGraphicsEnvironment().getDefaultScreenDevice()
					.getDefaultConfiguration();
		}
		return defaultConfiguration;
	}

	public static double getScaleFactor(GraphicsConfiguration gc)
	{
		return gc != null ? gc.getDefaultTransform().getScaleX() : 1;
	}

	public static double getScaleFactor(Graphics2D g)
	{
		return g != null ? getScaleFactor(g.getDeviceConfiguration()) : 1;
	}

	public static double getSystemScaleFactor()
	{
		if (systemScaleFactor == null)
		{
			if (OSTools.isMac() && JvmTools.getJavaRuntimeVersion() < 9)
			{
				final GraphicsDevice device = getDefaultConfiguration()
						.getDevice();
				try
				{
					Field field = device.getClass().getDeclaredField("scale");
					field.setAccessible(true);
					Object scale = field.get(device);
					LogTools.info("Mac device scale:" + scale);
					if (scale instanceof Integer)
						systemScaleFactor = (double) (int) scale;
				} catch (Exception ignored)
				{
				}
			}
			if (systemScaleFactor == null)
				systemScaleFactor = getScaleFactor(getDefaultConfiguration());
			LogTools.info("SystemScaleFactor:" + systemScaleFactor);
		}
		return systemScaleFactor;
	}

	public static BufferedImage createCompatibleImage(int width, int height,
			int transparency)
	{
		return getDefaultConfiguration().createCompatibleImage(width, height,
				transparency);
	}

	public static Graphics2D getHelpGraphics()
	{
		if (helpGraphics == null)
			helpGraphics = (new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB))
					.createGraphics();
		return helpGraphics;
	}

	public static FontMetrics getFontMetrics(Font font)
	{
		return getHelpGraphics().getFontMetrics(font);
	}

	public static Stroke getDottedStroke()
	{
		if (dottedStroke == null)
		{
			dottedStroke = new BasicStroke(1.5f, BasicStroke.CAP_BUTT,
					BasicStroke.JOIN_BEVEL, 0, new float[]{2, 2}, 0);
		}
		return dottedStroke;
	}

	public static Stroke getLargeStroke()
	{
		if (largeStroke == null)
			largeStroke = new BasicStroke(1.6f);
		return largeStroke;
	}

	public static Stroke getRoundStroke()
	{
		if (roundStroke == null)
			roundStroke = new BasicStroke(1.9f, BasicStroke.CAP_ROUND,
					BasicStroke.JOIN_ROUND);
		return roundStroke;
	}

	public static GraphicsConfig setupAAPainting(Graphics g)
	{
		final GraphicsConfig config = new GraphicsConfig(g);
		final Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
				RenderingHints.VALUE_STROKE_NORMALIZE);
		return config;
	}

	//	public static Shape applyClip(Graphics g, Shape shape)
	//	{
	//		Shape savedClip = g.getClip();
	//		if (savedClip == null)
	//			g.setClip(shape);
	//		else
	//		{
	//			Area intersectArea = new Area(shape);
	//			intersectArea.intersect(new Area(savedClip));
	//			g.setClip(intersectArea);
	//		}
	//		return savedClip;
	//	}

	//	public static Object[] setRenderingHints(Graphics2D g2d)
	//	{
	//		Object[] oldRenderingHints = new Object[]{
	//				g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING),
	//				g2d.getRenderingHint(RenderingHints.KEY_STROKE_CONTROL),};
	//		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
	//				RenderingHints.VALUE_ANTIALIAS_ON);
	//		g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
	//				MAC_USE_QUARTZ
	//						? RenderingHints.VALUE_STROKE_PURE
	//						: RenderingHints.VALUE_STROKE_NORMALIZE);
	//		return oldRenderingHints;
	//	}
	//
	//	public static void resetRenderingHints(Graphics2D g2d,
	//			Object[] oldRenderingHints)
	//	{
	//		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
	//				oldRenderingHints[0]);
	//		g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
	//				oldRenderingHints[1]);
	//	}

	public static void fillGradientColor(Graphics g, Color[] colors, int x,
			int y, int w, int h, int direction)
	{
		if (direction == 0) //HORIZONTAL
			fillHorGradient(g, colors, x, y, w, h);
		else if (direction == 1) //VERTICAL
			smoothFillVerGradient(g, colors, x, y, w, h);
	}

	public static void fillHorGradient(Graphics g, Color[] colors, int x, int y,
			int w, int h)
	{
		if (colors == null)
			return;
		int steps = colors.length;
		double dy = h / (double) (steps);
		if (dy <= 2.001)
		{
			int y1 = y;
			for (int i = 0; i < steps; i++)
			{
				int y2 = y + (int) Math.round(i * dy);
				g.setColor(colors[i]);
				if (i == (steps - 1))
				{
					g.fillRect(x, y1, w, y + h - y1);
				} else
				{
					g.fillRect(x, y1, w, y2 - y1);
				}
				y1 = y2;
			}
		} else
		{
			smoothFillHorGradient(g, colors, x, y, w, h);
		}
	}

	public static void smoothFillHorGradient(Graphics g, Color[] colors, int x,
			int y, int w, int h)
	{
		if (colors == null)
			return;
		Graphics2D g2D = (Graphics2D) g;
		Paint savedPaint = g2D.getPaint();
		int steps = colors.length;
		double dy = (double) h / (double) (steps - 1);
		int y1 = y;
		for (int i = 0; i < steps; i++)
		{
			int y2 = y + (int) Math.round(i * dy);
			if (i == (steps - 1))
			{
				g2D.setPaint(null);
				g2D.setColor(colors[i]);
				g2D.fillRect(x, y1, w, y + h - y1);
			} else
			{
				g2D.setPaint(new GradientPaint(0, y1, colors[i], 0, y2,
						colors[i + 1]));
				g2D.fillRect(x, y1, w, y2 - y1);
			}
			y1 = y2;
		}
		g2D.setPaint(savedPaint);
	}

	public static void smoothFillVerGradient(Graphics g, Color[] colors, int x,
			int y, int w, int h)
	{
		if (colors == null)
			return;
		Graphics2D g2D = (Graphics2D) g;
		Paint savedPaint = g2D.getPaint();
		int steps = colors.length;
		double dx = (double) w / (double) (steps);
		int x1 = x, x2 = 0;
		for (int i = 0; i <= steps; i++)
		{
			if (i == 0)
			{
				x2 = (int) (x1 + dx / 2);
				g2D.setColor(colors[i]);
				g2D.fillRect(x1, y, x2 - x1, h);
			} else if (i == steps)
			{
				g2D.setPaint(null);
				g2D.setColor(colors[i - 1]);
				g2D.fillRect(x1, y, x + w - x1, h);
			} else
			{
				x2 = (int) (x1 + dx);
				g2D.setPaint(new GradientPaint(x1, 0, colors[i - 1], x2, 0,
						colors[i]));
				g2D.fillRect(x1, y, x2 - x1, h);
			}
			x1 = x2;
		}
		g2D.setPaint(savedPaint);
	}

	public static void fillInverseHorGradient(Graphics g, Color[] colors, int x,
			int y, int w, int h)
	{
		if (colors == null)
			return;
		int steps = colors.length;
		double dy = (double) h / (double) steps;
		if (dy <= 2.001)
		{
			int y1 = y;
			for (int i = 0; i < steps; i++)
			{
				int y2 = y + (int) Math.round(i * dy);
				g.setColor(colors[colors.length - i - 1]);
				if (i == (steps - 1))
				{
					g.fillRect(x, y1, w, y + h - y1);
				} else
				{
					g.fillRect(x, y1, w, y2 - y1);
				}
				y1 = y2;
			}
		} else
		{
			smoothFillInverseHorGradient(g, colors, x, y, w, h);
		}
	}

	public static void smoothFillInverseHorGradient(Graphics g, Color[] colors,
			int x, int y, int w, int h)
	{
		if (colors == null)
			return;
		Graphics2D g2D = (Graphics2D) g;
		Paint savedPaint = g2D.getPaint();
		int steps = colors.length;
		double dy = (double) h / (double) steps;
		int y1 = y;
		for (int i = 0; i < steps; i++)
		{
			int y2 = y + (int) Math.round(i * dy);
			g.setColor(colors[colors.length - i - 1]);
			if (i == (steps - 1))
			{
				g2D.setPaint(null);
				g2D.setColor(colors[colors.length - i - 1]);
				g.fillRect(x, y1, w, y + h - y1);
			} else
			{
				g2D.setPaint(
						new GradientPaint(0, y1, colors[colors.length - i - 1],
								0, y2, colors[colors.length - i - 2]));
				g.fillRect(x, y1, w, y2 - y1);
			}
			y1 = y2;
		}
		g2D.setPaint(savedPaint);
	}

	public static void fillVerGradient(Graphics g, Color[] colors, int x, int y,
			int w, int h)
	{
		if (colors == null)
			return;
		int steps = colors.length;
		double dx = (double) w / (double) steps;
		int x1 = x;
		for (int i = 0; i < steps; i++)
		{
			int x2 = x + (int) Math.round(i * dx);
			g.setColor(colors[i]);
			if (i == (steps - 1))
			{
				g.fillRect(x1, y, x + w - x1, h);
			} else
			{
				g.fillRect(x1, y, x2 - x1, h);
			}
			x1 = x2;
		}
	}

	public static void fillInverseVerGradient(Graphics g, Color[] colors, int x,
			int y, int w, int h)
	{
		if (colors == null)
			return;
		int steps = colors.length;
		double dx = (double) w / (double) steps;
		int x1 = x;
		for (int i = 0; i < steps; i++)
		{
			int x2 = x + (int) Math.round(i * dx);
			g.setColor(colors[colors.length - i - 1]);
			if (i == (steps - 1))
			{
				g.fillRect(x1, y, x + w - x1, h);
			} else
			{
				g.fillRect(x1, y, x2 - x1, h);
			}
			x1 = x2;
		}
	}

	public static void drawSmoothString(Graphics g, String text, int x, int y)
	{
		Graphics2D g2D = (Graphics2D) g;
		Object savedRenderingHint = g2D
				.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
		g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
				RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g2D.drawString(text, x, y);
		g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
				savedRenderingHint);
	}

	//	public static void drawStringUnderlineCharAt(Graphics g, String text,
	//			int underlinedIndex, int x, int y)
	//	{
	//		Graphics2D g2d = (Graphics2D) g;
	//		Object savedRenderingHint = g2d
	//				.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
	//		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
	//				RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
	//		BasicGraphicsUtils.drawStringUnderlineCharAt(g, text, underlinedIndex,
	//				x, y);
	//		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
	//				savedRenderingHint);
	//	}

	public static void drawIconSymbol(Graphics g, Color color,
			Color shadowColor, int x, int y, int w, int h)
	{
		if (g == null || color == null)
			return;
		Graphics2D g2d = (Graphics2D) g;
		g2d.translate(x, y);
		int lw = (h > 22) ? 3 : 2;
		int dx = (w / 5) + 2;
		int dy = dx;
		Stroke savedStroke = g2d.getStroke();
		g2d.setStroke(new BasicStroke(lw, BasicStroke.CAP_BUTT,
				BasicStroke.JOIN_MITER));
		if (shadowColor != null)
		{
			g2d.setColor(shadowColor);
			g2d.drawLine(dx + 1, h - dy, w - dx + 1, h - dy);
		}
		g2d.setColor(color);
		g2d.drawLine(dx, h - dy - 1, w - dx, h - dy - 1);
		g2d.setStroke(savedStroke);
		g2d.translate(-x, -y);
	}

	public static void drawMaxSymbol(Graphics g, Color color, Color shadowColor,
			int x, int y, int w, int h)
	{
		if (g == null || color == null)
			return;
		Graphics2D g2d = (Graphics2D) g;
		g2d.translate(x, y);
		int lw = (h > 22) ? 2 : 1;
		int dx = (w / 5) + 1;
		int dy = (h / 5) + 2;
		Stroke savedStroke = g2d.getStroke();
		g2d.setStroke(new BasicStroke(lw, BasicStroke.CAP_BUTT,
				BasicStroke.JOIN_MITER));
		if (shadowColor != null)
		{
			g2d.setColor(shadowColor);
			g2d.drawRect(dx + 1, dy + 1, w - (2 * dx), h - (2 * dy));
			g2d.drawLine(dx + 1, dy + lw + 1, w - dx, dy + lw + 1);
		}
		g2d.setColor(color);
		g2d.drawRect(dx, dy, w - (2 * dx), h - (2 * dy));
		g2d.drawLine(dx + 1, dy + lw, w - dx, dy + lw);
		g2d.setStroke(savedStroke);
		g2d.translate(-x, -y);
	}

	public static void drawMinSymbol(Graphics g, Color color, Color shadowColor,
			int x, int y, int w, int h)
	{
		if (g == null || color == null)
			return;
		Graphics2D g2d = (Graphics2D) g;
		g2d.translate(x, y);
		int lw = (h > 22) ? 2 : 1;
		int delta = w / 4;
		w = Math.min(w, h) - 6;
		h = w;
		int x1 = 3;
		int y1 = 3;
		int w1 = w - delta;
		int h1 = h - delta;
		int x2 = delta + 2;
		int y2 = Math.max(delta + 2, y1 + (2 * lw) + 1);
		int w2 = w - delta;
		int h2 = h - delta;
		Shape savedClip = g2d.getClip();
		Stroke savedStroke = g2d.getStroke();
		g2d.setStroke(new BasicStroke(lw, BasicStroke.CAP_BUTT,
				BasicStroke.JOIN_MITER));
		Area clipArea = new Area(savedClip);
		clipArea.subtract(new Area(new Rectangle2D.Double(x2, y2, w2, h2)));
		g2d.setClip(clipArea);
		paintRect(g2d, x1, y1, w1, h1, lw, color, shadowColor);
		g2d.setClip(savedClip);
		paintRect(g2d, x2, y2, w2, h2, lw, color, shadowColor);
		g2d.setStroke(savedStroke);
		g2d.translate(-x, -y);
	}

	private static void paintRect(Graphics2D g2D, int x, int y, int w, int h,
			int lw, Color color, Color shadowColor)
	{
		if (shadowColor != null)
		{
			g2D.setColor(shadowColor);
			g2D.drawRect(x + 1, y + 1, w, h);
			g2D.drawLine(x + 1, y + lw + 1, x + w + 1, y + lw + 1);
		}
		g2D.setColor(color);
		g2D.drawRect(x, y, w, h);
		g2D.drawLine(x, y + lw, x + w, y + lw);
	}

	public static void drawCloseSymbol(Graphics g, Color color,
			Color shadowColor, int x, int y, int w, int h)
	{
		if (g == null || color == null)
			return;
		Graphics2D g2d = (Graphics2D) g;
		g2d.translate(x, y);
		int lw = (h > 22) ? 3 : 2;
		int dx = (w / 5) + 2;
		int dy = dx;

		Stroke savedStroke = g2d.getStroke();
		Object savedRenderingHint = g2d
				.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		g2d.setStroke(new BasicStroke(lw, BasicStroke.CAP_ROUND,
				BasicStroke.JOIN_BEVEL));
		if (shadowColor != null)
		{
			g2d.setColor(shadowColor);
			g2d.drawLine(dx + 1, dy + 1, w - dx + 1, h - dy + 1);
			g2d.drawLine(w - dx + 1, dy + 1, dx + 1, h - dy + 1);
		}
		g2d.setColor(color);
		g2d.drawLine(dx, dy, w - dx, h - dy);
		g2d.drawLine(w - dx, dy, dx, h - dy);

		g2d.setStroke(savedStroke);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				savedRenderingHint);
		g2d.translate(-x, -y);
	}

	public static void drawCheckmark(Graphics2D g)
	{
		Stroke saveStroke = g.getStroke();
		g.setStroke(getRoundStroke());
		g.draw(ShapeTools.getCheckmark());
		g.setStroke(saveStroke);
	}

	public static void drawBottomRightRoundRect(Graphics g, int x, int y, int w,
			int h, int arc)
	{
		int gap = arc / 2 + 1;
		int x2 = x + w, y2 = y + h;
		g.drawLine(x, y2, x2 - gap, y2);
		g.drawArc(x2 - arc, y2 - arc, arc, arc, -90, 90);
		g.drawLine(x2, y, x2, y2 - gap);
	}

	public static void fillTriangle(Graphics g, int x, int y, int w, int h,
			int direction)
	{
		int[] xs = new int[3];
		int[] ys = new int[3];
		switch (direction)
		{
			//NORTH
			case 1:
				xs[0] = x + (w / 2);
				ys[0] = y;
				xs[1] = x;
				ys[1] = y + h;
				xs[2] = x + w;
				ys[2] = y + h;
				break;
			//SOUTH
			case 5:
				xs[0] = x;
				ys[0] = y;
				xs[1] = x + w;
				ys[1] = y;
				xs[2] = x + (w / 2);
				ys[2] = y + h;
				break;
			//WEST
			case 7:
				xs[0] = x + w;
				ys[0] = y;
				xs[1] = x;
				ys[1] = y + (h / 2);
				xs[2] = x + w;
				ys[2] = y + h;
				break;
			//EAST
			case 3:
				xs[0] = x;
				ys[0] = y;
				xs[1] = x + w;
				ys[1] = y + (h / 2);
				xs[2] = x;
				ys[2] = y + h;
		}
		g.drawPolygon(xs, ys, 3);
		g.fillPolygon(xs, ys, 3);
	}

	public static void paintGrip(Graphics2D g2d, int x, int y, int width,
			int height, boolean horizontal, int dotCount, int dotSize, int gap,
			boolean centerPrecise)
	{
		int gripSize = (dotSize * dotCount) + ((gap * (dotCount - 1)));

		// calculate grip position
		float gx;
		float gy;
		if (horizontal)
		{
			gx = x + Math.round((width - gripSize) / 2f);
			gy = y + ((height - dotSize) / 2f);

			if (!centerPrecise)
				gy = Math.round(gy);
		} else
		{
			// vertical
			gx = x + ((width - dotSize) / 2f);
			gy = y + Math.round((height - gripSize) / 2f);

			if (!centerPrecise)
				gx = Math.round(gx);
		}

		// paint dots
		for (int i = 0; i < dotCount; i++)
		{
			g2d.fill(new Ellipse2D.Float(gx, gy, dotSize, dotSize));
			if (horizontal)
				gx += dotSize + gap;
			else
				gy += dotSize + gap;
		}
	}
	public static void drawStringUnderlineCharAt(Graphics g, String text,
			int underlinedIndex, int x, int y)
	{
		Graphics2D g2d = (Graphics2D) g;
		Object savedRenderingHint = g2d
				.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
				RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		BasicGraphicsUtils.drawStringUnderlineCharAt(g, text, underlinedIndex,
				x, y);
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
				savedRenderingHint);
	}
	public static void fillComponent(Graphics g, Component c, Icon texture)
	{
		if (texture == null)
			return;
		int x, y;
		int w = c.getWidth();
		int h = c.getHeight();
		int tw = texture.getIconWidth();
		int th = texture.getIconHeight();
		Point p = SwingTools.getRelLocation(c);
		y = -p.y;
		while (y < h)
		{
			x = -p.x;
			while (x < w)
			{
				texture.paintIcon(c, g, x, y);
				x += tw;
			}
			y += th;
		}
	}
}
